package at.fhooe.mc.master.clc.server.businesslogic;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.datanucleus.store.mapped.mapping.DateMapping;

import com.google.visualization.datasource.base.TypeMismatchException;
import com.google.visualization.datasource.datatable.ColumnDescription;
import com.google.visualization.datasource.datatable.DataTable;
import com.google.visualization.datasource.datatable.TableRow;
import com.google.visualization.datasource.datatable.value.DateTimeValue;
import com.google.visualization.datasource.datatable.value.NumberValue;
import com.google.visualization.datasource.datatable.value.ValueType;
import com.google.visualization.datasource.render.JsonRenderer;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeZone;

import at.fhooe.mc.master.clc.server.dataaccess.DAOFactory;
import at.fhooe.mc.master.clc.server.dataaccess.dao.IMetricsDao;
import at.fhooe.mc.master.clc.server.dataaccess.dao.objectify.MetricsObjectifyDao;
import at.fhooe.mc.master.clc.shared.domain.FrameworkID;
import at.fhooe.mc.master.clc.shared.domain.metrics.IMetric;
import at.fhooe.mc.master.clc.shared.domain.metrics.IMetricsRequest;
import at.fhooe.mc.master.clc.shared.domain.metrics.IMetricsResponse;
import at.fhooe.mc.master.clc.shared.domain.metrics.MetricKey;
import at.fhooe.mc.master.clc.shared.domain.metrics.MetricsResponse;

public class MetricsDataAdapter implements IMetricsDataAdapter
{
	private IMetricsDao dao = DAOFactory.getDAOFactory().getMetricsDao();

	private long convertHoursToMilliseconds(long hours)
	{
		return convertMinutesToMilliseconds(hours * 60);
	}

	private long convertMinutesToMilliseconds(long minutes)
	{
		return minutes * 60 * 1000;
	}
	
	/**
	 * Returns a DateTimeValue formatted for the correct time zone.
	 * @param date the date of the measurement
	 * @param offsetInHours the offset in hours from the time zone
	 * @return a {@link GregorianCalendar} time value representing the given {@link Date}.
	 */
	private DateTimeValue getDateTimeValueFromDate(Date date, int offsetInHours)
	{
		GregorianCalendar c = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
		c.setTime(new Date(date.getTime() + convertHoursToMilliseconds(offsetInHours)));

		return new DateTimeValue(c);
	}
	
	/**
	 * Converts the given structure data into a JSON table (using the datasource library)
	 * that is then just given to the graph (visualization API) in the client.
	 * 
	 * @param dataMap the map containing the structure data
	 * @param frameworkFilter the frameworks that were supposed to be loaded 
	 * (needed to add null values in case nothing was loaded for the framework).
	 * @return a map from string to string that contains the graph data 
	 * (under "data" key) and other graph information like the graph title or axis titles
	 */
	private Map<MetricKey, Map<String, String>> convertDataMapToDataTable(
			Map<MetricKey, Map<Date, Map<FrameworkID, IMetric>>> dataMap,
			List<FrameworkID> frameworkFilter)
	{
		Map<MetricKey, Map<String, String>> dataTableMap = new LinkedHashMap<MetricKey, Map<String,String>>();
		
		for (MetricKey mKey : dataMap.keySet())
		{
			String titel = mKey.getLabel() + " for: ";
			DataTable dataTable = new DataTable();
			
			// add the date column
			ColumnDescription dateCol = new ColumnDescription("date", ValueType.DATETIME, "Date");
			dateCol.setCustomProperty("role", "domain");
			dataTable.addColumn(dateCol);
			
			if (dataMap.get(mKey) != null)
			{
				// add the data columns
				for (FrameworkID pfid : frameworkFilter)
				{
					titel += pfid.getLabel() + ", ";
					
					ColumnDescription dataCol = new ColumnDescription(pfid.toString(), ValueType.NUMBER, pfid.getLabel());
					dataCol.setCustomProperty("role", "data");
					dataTable.addColumn(dataCol);
				}
				
				// add the data
				for (Date d : dataMap.get(mKey).keySet())
				{
					TableRow row = new TableRow();
					
					// TODO: This needs better handling. At the moment it just converts to CET
					int timeZoneOffset = 1;
					
					// add the date (domain column)
					row.addCell(getDateTimeValueFromDate(d, timeZoneOffset));
					
					// fill the famework columns - if data is there (otherwise null entry)
					for (FrameworkID pfid : frameworkFilter)
					{
						if (dataMap.get(mKey).get(d).get(pfid) != null)
						{
							row.addCell(dataMap.get(mKey).get(d).get(pfid).getValue().getValue());
						}
						else
						{
							row.addCell(NumberValue.getNullValue());
						}
					}
					
					try
					{
						dataTable.addRow(row);
					} catch (TypeMismatchException e)
					{
						e.printStackTrace();
					}
				} 
				
			} // end if map for data key is not null
			
			String sDataTable = JsonRenderer.renderDataTable(dataTable, true, true,
					true).toString();
			
			Map<String, String> innerDataMap = new HashMap<String, String>();
			titel = titel.substring(0, titel.lastIndexOf(","));
			innerDataMap.put("data", sDataTable);
			innerDataMap.put("vAxisTitle", "Milliseconds");
			innerDataMap.put("graphTitle", titel);
			
			dataTableMap.put(mKey, innerDataMap);
			
		} // end foreach metrics key in data map
		
		return dataTableMap;
	}
	
	@Override
	public IMetricsResponse requestMetricsData(IMetricsRequest request)
	{
		Date startDate = request.getStartDate();
		Date endDate = request.getEndDate();
		
		if (startDate == null || endDate == null)
			return null;
		
		List<FrameworkID> frameworkFilter = request.getFrameworkIds();
		List<MetricKey> metricKeyFilter = request.getMetricsKeys();
		
		// init the data map
		// maps:
		// metrics-key -> cycle-time-stamp -> framework
		Map<MetricKey, Map<Date, Map<FrameworkID, IMetric>>> dataMap = 
				new LinkedHashMap<MetricKey, Map<Date,Map<FrameworkID,IMetric>>>();
		
		
		// retrieve the filtered metrics-values
		for (MetricKey mKey : metricKeyFilter)
		{
			List<? extends IMetric> metrics = dao.getMetrics(startDate, endDate, null, mKey);
			
			for (IMetric m : metrics)
			{
				if (dataMap.get(mKey) == null)
					dataMap.put(mKey, new LinkedHashMap<Date, Map<FrameworkID,IMetric>>());
				
				if (dataMap.get(mKey).get(m.getTimeStamp()) == null)
					dataMap.get(mKey).put(m.getTimeStamp(), new LinkedHashMap<FrameworkID, IMetric>());
				
				dataMap.get(mKey).get(m.getTimeStamp()).put(m.getFrameworkId(), m);
			}
		}
		
		// convert the values to datatable and return
		return new MetricsResponse(convertDataMapToDataTable(dataMap, frameworkFilter));
	}

}
